#include "portal_sql.h"
#include "portal.h"
#include "portal_md5.h"
#include "portal_nas.h"
#include "portal_http.h"
#include "portal_pkt.h"

struct portal_args_s portal_args;
bool portal_debug;

void portal_hex_to_str(uchar *hex, int len, char *str)
{
    int i;
    char tmp[10];

    for (i = 0; i < len; i++) {
        snprintf(tmp, sizeof(tmp), "%02x", hex[i]);
        strcat(str, tmp);
    }
}

void portal_str_to_hex(char *string, int len, uchar *data)
{
    int i;

    for (i = 0; i < len; i += 2) {
        sscanf((const char *)&string[i], "%02X", (unsigned int *)&data[i / 2]);
    }
}

static int portal_init_httpd(ushort http_port)
{
    int fd;
    struct sockaddr_in sin;

    fd = socket(AF_INET, SOCK_STREAM, 0);
    if (fd <0) {
        PORTAL_DEBUG("init httpd sock failed, errno %d", errno);
        return -errno;
    }

    memset(&sin, 0, sizeof(sin));
    sin.sin_family = AF_INET;
    sin.sin_port = htons(http_port);
    sin.sin_addr.s_addr = htonl(INADDR_ANY);
    if (bind(fd, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
        PORTAL_DEBUG("bind httpd sock failed ,errno %d", errno);
        return -errno;
    }

    if (listen(fd, 256) < 0) {
        PORTAL_DEBUG("listen httpd sock failed, errno %d", errno);
        return -errno;
    }

    return fd;
}

static int portal_init_epoll(int fdsize)
{
    int fd;

    fd = epoll_create(fdsize);
    if (fd < 0) {
        PORTAL_DEBUG("create epoll fd failed, errno %d", errno);
        return -errno;
    }

    return fd;
}

static int portal_init_udp_fd(ushort portal_port)
{
    int fd;
    struct sockaddr_in sin;

    fd = socket(AF_INET, SOCK_DGRAM, 0);
    if (fd < 0) {
        PORTAL_DEBUG("init portal fd failed, errno %d", errno);
        return -errno;
    }

    memset(&sin, 0, sizeof(sin));
    sin.sin_family = AF_INET;
    if (portal_port) {
        sin.sin_port = htons(portal_port);
    }
    sin.sin_addr.s_addr = htonl(INADDR_ANY);
    if (bind(fd, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
        PORTAL_DEBUG("create portal sock failed, errno %d", errno);
        return -errno;
    }

    return fd;
}

static void portal_task_main()
{
    int nfds, i, fd;
    struct epoll_event ev, *events = portal_args.epoll_events;

    ev.events = EPOLLIN;
    ev.data.fd = portal_args.httpd_fd;
    epoll_ctl(portal_args.epoll_fd, EPOLL_CTL_ADD, portal_args.httpd_fd, &ev);
    ev.events = EPOLLIN;
    ev.data.fd = portal_args.portal_fd;
    epoll_ctl(portal_args.epoll_fd, EPOLL_CTL_ADD, portal_args.portal_fd, &ev);
    ev.events = EPOLLIN;
    ev.data.fd = portal_args.portal_send_fd;
    epoll_ctl(portal_args.epoll_fd, EPOLL_CTL_ADD, portal_args.portal_send_fd, &ev);
    ev.events = EPOLLIN;
    ev.data.fd = portal_args.bind_portal_fd;
    epoll_ctl(portal_args.epoll_fd, EPOLL_CTL_ADD, portal_args.bind_portal_fd, &ev);
    while (1) {
        nfds = epoll_wait(portal_args.epoll_fd, events, MAX_EPOLL_EVENT, -1);
        if (nfds <= 0) {
            continue;
        }

        for (i = 0; i < nfds; i++) {
            fd = events[i].data.fd;
            if (fd == portal_args.httpd_fd) {
                portal_add_listen_fd(fd);
            } else if (fd == portal_args.portal_fd) {
                portal_pro_auth(fd);
            } else if (fd == portal_args.bind_portal_fd) {
                portal_pro_auth(fd);
            } else if (fd == portal_args.portal_send_fd) {
                portal_pro_auth(fd);
            } else {
                portal_pro_http_req(fd);
            }
        }

    }
}

static bool portal_service_init()
{
    if ((portal_args.epoll_fd = portal_init_epoll(MAX_EPOLL_EVENT)) < 0) {
        return FALSE;
    }

    if ((portal_args.httpd_fd = portal_init_httpd(portal_args.http_port)) < 0) {
        close(portal_args.epoll_fd);
        return FALSE;
    }

    if ((portal_args.portal_fd = portal_init_udp_fd(portal_args.portal_port)) < 0) {
        close(portal_args.epoll_fd);
        close(portal_args.httpd_fd);
        return FALSE;
    }

    if ((portal_args.portal_send_fd = portal_init_udp_fd(portal_args.portal_send_port)) < 0) {
        close(portal_args.epoll_fd);
        close(portal_args.httpd_fd);
        close(portal_args.portal_fd);
        return FALSE;
    }

    if ((portal_args.bind_portal_fd = portal_init_udp_fd(portal_args.bind_portal_port)) < 0) {
        close(portal_args.epoll_fd);
        close(portal_args.httpd_fd);
        close(portal_args.portal_fd);
        close(portal_args.portal_send_fd);
        return FALSE;
    }

    return TRUE;
}

static void portal_usage()
{
    printf("portal: illegal option -- h\n");
    printf("usage: [-a authen mode pap or chap def chap]\n");
    printf("       [-b bind_portal_port def 50200]\n");
    printf("       [-i portal_local_ip][-n fmt acname=acip or acip] \n");
    printf("       [-p portal_port def 50100] [-s portal_send_port def 50300]\n");
    printf("       [-t http_port def 80] [-d debug mode]\n");
    printf("       [-k configure portal decrypt key]\n");
    printf("       [-S switch bind ack and req challenge rsp]\n");
    printf("       [-q sql database name]\n");
}

static bool portal_getopt(int argc, char *argv[])
{
    int ch;
    char *eq;
    bool ip_conf = FALSE, key_conf = FALSE;
    IPADDRTYPE acip;

    while ((ch = getopt(argc, argv, "ab:i:p:s:t:n:hdk:Sq:")) != -1) {
        switch (ch) {
        case 'S':
            portal_args.portal_mac_sms_shuffle = TRUE;
            break;
        case 'd':
            portal_debug = TRUE;
            break;
        case 'h':
            portal_usage();
            return FALSE;
        case 'a':
            printf("authen mode pap.\n");
            portal_args.authen_mode = 1;
            break;
        case 'b':
            portal_args.bind_portal_port = atoi(optarg);
            printf("bind portal port: %d.\n", portal_args.bind_portal_port);
            break;
        case 'i':
            if (!inet_aton(optarg, (struct in_addr *)&portal_args.portal_ip)) {
                printf("Invalid option for portal ip.\n");
                continue;
            }
            printf("portal ip: %s.\n", optarg);
            ip_conf = TRUE;
            break;
        case 'p':
            portal_args.portal_port = atoi(optarg);
            printf("portal port: %d.\n", portal_args.portal_port);
            break;
        case 's':
            portal_args.portal_send_port = atoi(optarg);
            printf("portal send port: %d.\n", portal_args.portal_send_port);
            break;
        case 't':
            portal_args.http_port = atoi(optarg);
            printf("http port: %d.\n", portal_args.http_port);
            break;
        case 'n':
            if ((eq = strchr(optarg, '='))) {
                if (!inet_aton(eq + 1, (struct in_addr *)&acip)) {
                    printf("Invalid option for ac ip.\n");
                    continue;
                }
                *eq = '\0';
                if (!portal_add_acip(optarg, acip)) {
                    printf("add ac ip failed.\n");
                    continue;
                }
                printf("add acname %s acip %s.\n", optarg, eq + 1);
            } else {
                if (!inet_aton(optarg, (struct in_addr *)&acip)) {
                    printf("Invalid option for ac ip.\n");
                    continue;
                }
                if (!portal_add_acip(NULL, acip)) {
                    printf("add ac ip failed.\n");
                    continue;
                }
                printf("add acip %s.\n", optarg);
            }
            break;
        case 'k':
            strncpy(portal_args.key, optarg, sizeof(portal_args.key));
            printf("key %s", portal_args.key);
            key_conf = TRUE;
            break;
        case 'q':
            if (strlen(optarg) >= sizeof(portal_args.portal_db)) {
                printf("database name(%s) too long, skip!\n", optarg);
                break;
            }
            snprintf(portal_args.portal_db, sizeof(portal_args.portal_db), "%s", optarg);
            break;
        default:
            printf("Invalid option.\n");
            continue;
        }
    }

    if (!ip_conf) {
        printf("please config portal local ip by option -i.\n");
        portal_usage();
        return FALSE;
    }

    if (!key_conf) {
        strncpy(portal_args.key, "12345678", sizeof(portal_args.key));
    }

    return TRUE;
}

int main(int argc, char *argv[])
{
    int i, cpu_num;

    memset(&portal_args, 0, sizeof(portal_args));
    portal_args.portal_port = 50100;
    portal_args.bind_portal_port = 50200;
    portal_args.portal_send_port = 50300;
    portal_args.http_port = 80;

    for (i = 0; i < FD_POLL_SIZE; i++) {
        INIT_LIST_HEAD(&portal_args.hlst[i]);
    }

    INIT_LIST_HEAD(&portal_args.lst);
    INIT_LIST_HEAD(&portal_args.v4_user);
    INIT_LIST_HEAD(&portal_args.v6_user);
    INIT_LIST_HEAD(&portal_args.acip_lst);
    INIT_LIST_HEAD(&portal_args.bind_lst);

    snprintf(portal_args.portal_db, sizeof(portal_args.portal_db), "%s", PORTAL_DB);

    if (!portal_getopt(argc, argv)) {
        return -1;
    }

    if (!portal_sql_init()) {
        return -1;
    }

    portal_make_acip_md5();

    cpu_num = sysconf(_SC_NPROCESSORS_ONLN);
    printf("online cpu number is %d\n",cpu_num);

    if (!portal_service_init()) {
        return -1;
    }

    portal_task_main();

    return 0;
}

